package com.testing.sunnycommon.security.config;

import com.testing.sunnycommon.security.filter.SunnyJwtAuthenticationFilter;
import com.testing.sunnycommon.security.handler.SunnyAccessDeniedHandler;
import com.testing.sunnycommon.security.handler.SunnyAuthenticationEntryPoint;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.BeanIds;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;


/**
 * @Classname SunnySecurityConfig
 * @Description 类型说明
 * @Date 2023/6/30 20:55
 * @Created by 特斯汀Roy
 */
@Configuration
@EnableWebSecurity
public class SunnySecurityConfig {

    @Autowired
    SunnyAccessDeniedHandler sunnyAccessDeniedHandler;

    @Autowired
    SunnyAuthenticationEntryPoint sunnyAuthenticationEntryPoint;

    @Autowired
    SunnyJwtAuthenticationFilter sunnyJwtAuthenticationFilter;

    //在controller以及filter中需要使用的authenticationManager对象的bean构造方法。
    @Bean(name = BeanIds.AUTHENTICATION_MANAGER)
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }


    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }


//    //如果不配置securityFilterChain，默认情况下，security是不暴露login接口的，而是使用login界面完成登录
//    //用postman去请求的时候，会是unAuthorized。
//    //依然基于原本的security的login接口和界面来进行登录操作。
//    @Bean
//    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
//        //关闭csrf限制
//        http.csrf().disable()
//                //开始进行security的限制配置
//                .authorizeRequests()
//                //如果路径匹配login，就可以允许所有用户访问，未登录也能访问
//                .antMatchers("/login")
//                .permitAll()
//                //其它的请求
//                .anyRequest()
//                //需要鉴权
//                .authenticated()
//                //另外
//                .and()
//                //基于表单进行登录
//                .formLogin()
//                //使用/login路径作为访问登录的接口
//                .loginProcessingUrl("/login")
//                //默认情况下如果登录成功，跳转到/
//                .successForwardUrl("/")
//                //自定义成功之后的返回操作,回调函数指定
//                .successHandler(new AuthenticationSuccessHandler() {
//                    @Override
//                    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
//                        //security完成鉴权之后，自动去设置authentication的信息。
//                        //鉴权的结果信息中的用户内容
//                        Object principal = authentication.getPrincipal();
//                        response.setContentType("application/json;charset=utf-8");
//                        Result<Object> success = Result.success(principal);
//                        PrintWriter writer = response.getWriter();
//                        String resultJsonString = new ObjectMapper().writeValueAsString(success);
////                        writer.write("登录成功"+principal);
//                        writer.write(resultJsonString);
//                        writer.flush();
//                        writer.close();
//                    }
//                })
//        ;
//
//        return http.build();
//    }


    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        //使用lambda表达式，相当于箭头函数的作用，定义一个接口中需要实现的方法的简化方式
        return web -> web.ignoring().antMatchers(
                //放行swagger相关资源
                "/swagger-ui/**", "/swagger-resources/**", "/v3/api-docs",
                //放行websocket相关资源
                "/sunnyWebSocket/**", "/app/**");
    }


    //自定义接口来完成鉴权的操作
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.csrf().disable()
                //设置session创建策略为无状态创建，也就是不再在session中存储鉴权信息，让即便完成了登录的cookie也不再有效
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                //放行自己的登录和注册接口
                .antMatchers("/sunnyUser/login", "/sunnyUser/register", "/sunnyUser/avatar/**")
                .permitAll()
                .anyRequest()
                //所有的请求是需要验证已经通过了security的鉴权的
                //原本是Security接到cookie自己去鉴权，或者是登录的时候进行设置context中完成鉴权
                .authenticated()
                .and()
                //禁用原本的登录操作
                .formLogin()
                .disable()
                // 在每个接口调用之前需要做的事情，
                // 在接口交给security判断是否鉴权通过之前，
                // 从原本security自己验证cookie变成判断请求头中的token是否能够通过鉴权
                // 完成security的鉴权设置，如果token是有效的，并且security没有记录这个健全信息，就补充记录一下
                //覆盖的场景其实是过一段时间之后重新进行访问，因为token是一天有效的。
                // 再交给security自动检查
                .addFilterBefore(sunnyJwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
                //进行异常情况的处理，需要用到两个handler,类似于successHandler，自定义出现异常的时候需要返回的信息
                .exceptionHandling()
                //进入鉴权流程入口的异常回调函数
                .authenticationEntryPoint(sunnyAuthenticationEntryPoint)
                //鉴权失败，权限禁止的回调函数
                .accessDeniedHandler(sunnyAccessDeniedHandler);

        return http.build();
    }


}
